/*-----------------------------------------------------------------------
 * MediaFrame is an Open Source streaming media platform in Java 
 * which provides a fast, easy to implement and extremely small applet 
 * that enables to view your audio/video content without having 
 * to rely on external player applications or bulky plug-ins.
 *
 *-----------------------------------------------------------------------
 *
 * We changed a lot of code and added a lot of functionality.
 * This includes, but not limited to, the following changes:
 * 1. The project was renamed to MediaFrame;
 * 2. The connection speed detection procedure was added;
 * 3. The JavaScript API functions were added;
 * 4. The pre and post image support functionality was added;
 * 5. The ability to save movie into the local disk was added;
 * 6. The inner buffer for a movie file was added;
 * 7. The click-through functionality was added;    
 * 8. The .zip files support was added;    
 * 9. The realtime feedback agent functionality was added.    
 * For the full list of the current functionality please visit 
 * the following web page: http://mediaframe.org/
 *    
 * 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev
 *
 *-----------------------------------------------------------------------
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 *
 * Alan Blount 3/7/00
 */

package mediaframe.mpeg1.audio.player;

import java.io.*;
import mediaframe.mpeg1.audio.decoder.*;
import sun.audio.*;

/**
 * The <code>EightBitAudioDevice</code> class implements an audio
 * device by using the sun.audio.AudioPlayer class.
 * 
 * @author Alan Blount
 */
public class EightBitAudioDevice implements Runnable, AudioDevice{
	/** The size of the circle buffer in the pipe. */
	private static final int outputCapacity = 8192 * 4 * 8;

	/** The temporary buffer for storing the audio samples. */
	private short[] byteBuf = new short[1024];
	
	/** The input stream in the pipe. */
	private BigPipedInputStream pis;
	/** The output stream in the pipe. */
	private BigPipedOutputStream pos;


	/** <tt>True</tt> if this audio device is opened, <tt>false</tt> otherwise. */
	private	boolean			open = false;
	
	/** The decoder that provides audio data to this audio device. */
	private Decoder			decoder = null;
	
	/**
	 * Opens this audio device. 
	 * 
	 * @param decoder	the decoder that will provide audio data
	 *					to this audio device.
	 * @throws JavaLayerException if the audio device could not be opened.
	 */
	public synchronized void open(Decoder decoder) throws JavaLayerException
	{
		if (! open)
		{
			this.decoder = decoder;

			pis = new BigPipedInputStream(outputCapacity);
			pos = new BigPipedOutputStream(pis);

			Thread device_thread = new Thread(this, "Eight Bit Audio Device");
    		device_thread.start();

			// Set decoder to create 8-bit ulaw audio
			decoder.getParams().setDownconvert();

			open = true;
		}
	}
	
	/**
	 * Returns <tt>true</tt> because the <code>EightBitAudioDevice</code> audio device is a   
	 * crappy eight-bit eight khz ulaw decoder (browser).
	 */
	public boolean isEightBitEightKhzMuLaw() {
		return true;
	}
		
	/**
	 * Starts the <code>sun.audio.AudioPlayer</code> in the "Eight Bit Audio Device" thread.
	 * This method is called by the JRE after this audio device is opened and the 
	 * "Eight Bit Audio Device" thread is started.
	 */
	public void run() {
		try
		{
//			System.out.print("Starting sun.audio.........");
			AudioPlayer.player.start(pis);
//			System.out.println("started!");
		}
		catch(Exception e)
		{
			System.out.println("Could not start AudioPlayer (sun.audio)");
		}
	}
	
	/**
	 * Closes this audio device. If the device is currently playing 
	 * audio, playback is stopped immediately without flushing
	 * any buffered audio data. 
	 */
	public void close() { 
		if (open) {
			pis.playing = false;
			pis.close();
			pos.close();
			open = false;
			decoder = null;
		}
	}
	
	/**
	 * Writes audio data to this audio device. Audio data is
	 * assumed to be in the output format of the decoder. This
	 * method may return before the data has actually been sounded
	 * by the device if the device buffers audio samples.
	 * <p/> 
	 * If the audio device is not open, this method does nothing. 
	 * 
	 * @param samples	the samples to write to the audio device.
	 * @param offset	the offset into the array of the first sample to write.
	 * @param len		the number of samples from the array to write. 
	 * @throws JavaLayerException if the audio data could not be
	 *			written to the audio device. 
	 */
	public void write(short[] samples, int offset, int len) throws JavaLayerException {		if(!open)
			return;

		short[] output = getShortArray(len);
		int count = toShortArray(samples, offset, len, output, decoder.getOutputChannels() > 1);
		
		try {

//			System.out.println("Audio Writing " + count + " buf fill: " + pis.available() + " inp: " + pis.getInp() + " outp:" + pis.getOutp());

			while(pis.freeInBuffer() <= (len * 2)) {
				if(!open)
					return;
				try {
					Thread.sleep(100);
				} catch (InterruptedException IE) {
				    throw new JavaLayerException (IE.toString());
				}
			}

			pos.write(output, 0, count);						
		} catch (IOException ex) {
			System.out.println("Trouble writing to pis/pos " + ex);		
			throw new JavaLayerException("No audio possible...", ex);
		}
		
		// System.out.println("writeImpl done");
	}
	
	/**
	 * Gets the temporary buffer that able to store up to <code>length</code> audio samples.
	 * @param length min size of the temporary buffer.
	 * @return the temporary buffer for storing the audio samples.
	 */
	protected short[] getShortArray(int length) {
		// The normal length for this routime is 420 bytes
		if (byteBuf.length < length) {
			byteBuf = new short[length + 1024];
		}
		return byteBuf;
	}
	
	/**
	 * Copies up to <code>len</code> audio samples from the <code>samples</code> array 
	 * starting at the offset <code>offset</code> into the <code>b</code> array.
	 * Mix stereo samples into the mono sample if the audio decoder provides stereo data.
	 * @param samples the audio data.
	 * @param offset the start offset in the data.
	 * @param len the number of samples to copy. 
	 * @param b the destination array of audio data into which the data is copied.
	 * @param stereoToMono <tt>true</tt> if the audio decoder provides stereo data  
	 * @return the total number of samples copied into the <code>b</code> array. 
	 */
	int toShortArray(short[] samples, int offset, int len, short[] b, boolean stereoToMono) {
		int idx = 0, preMu, signBit;

		while (len-- > 0) {

			if (stereoToMono) {
				preMu = (samples[offset++] + samples[offset++]) >> 1;
				len--;
			} else {
				preMu = samples[offset++];
			}

			// Cap preMu
			preMu = preMu < -32767 ? -32767 : preMu > 32767 ? 32767 : preMu;

			b[idx++] =  (short) preMu;	    }
	    return idx;
	}
		
	
	/**
	 * Waits for any buffered audio samples to be played by the
	 * audio device. This method should only be called prior 
	 * to closing the device. 
	 */
	public void flush() {
		// FIXME
	}
/*	
	public int getBufferPosition() {		
		try
		{
			return pis.available() / 8;
		}
		catch(Exception e)
		{
			System.out.println("getPosition--> "+e);
		}
		return -1;
	}

	public int getPosition()
	{
		return -1;
	}

	public float getFillRatio() {
		float ret = 0;
		try {
			ret = (float)pis.available() / (float) outputCapacity;
		} catch (IOException ex) {
		}
		return ret;
	}

*/		

	/**
	 * Start player playing.
	 */
	public void play() {

		pis.playing = true;
	}

	/**
	 * Pause the Player.
	 */
	public void pause() {

		pis.playing = false;
	}
	
	/**
	 * Stop player playing.
	 */
	public void stop() {

		pis.playing = false;
		System.out.println("Audio Device is stopped!");
	}

	/**
	 * Sets the volume (in the range 0..1) of the audio playback. 
	 * @param volume the volume to set.
	 */
	public void setVolume(float volume) {		
		pis.Volume = (int) (volume * (float)256);
	}

	/**
	 * Sets the mute state of the audio playback.
	 * @param mute the mute state (<tt>true</tt> if the mute on and the player doesn't play the audio). 
	 */
	public void setMute(boolean mute) {

		pis.mute = mute;
	}

	/**
	 * Is called if the end of file has been reached.
	 */
	public void endOfFile() {
		if(open) pis.bufferFilled = true;			
	}

}
